/*
 * Copyright (c) 2010-2020 Belledonne Communications SARL.
 *
 * This file is part of linphone-android
 * (see https://www.linphone.org).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.newlink.building.activities.call.viewmodels

import android.content.Context
import android.os.SystemClock
import android.os.Vibrator
import android.text.Editable
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.newlink.building.common_base.base.BaseApplication
import com.newlink.building.common_base.constant.NL_Key
import com.newlink.building.library_rino.utils.RinoAudioUtils
import com.newlink.building.NL_App
import com.newlink.building.NL_App.Companion.context
import com.newlink.building.NL_App.Companion.coreContext
import com.newlink.building.R
import com.newlink.building.activities.main.dialer.Module_Phone_NumpadDigitListener
import com.newlink.building.adapter.OptionItem
import com.newlink.building.compatibility.Module_Phone_Compatibility
import com.newlink.building.contact.GenericContactViewModel
import com.newlink.building.enums.Module_Phone_DeviceType
import com.newlink.building.manager.Module_Phone_VoipController
import com.newlink.building.utils.Module_Phone_AudioRouteUtils
import com.newlink.building.utils.Module_Phone_Event
import com.newlink.building.utils.Module_Phone_FileUtils
import com.newlink.building.utils.Module_Phone_LinphoneUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.linphone.core.AudioDevice
import org.linphone.core.Call
import org.linphone.core.CallListenerStub
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.Factory
import org.linphone.core.MediaDirection
import org.linphone.core.tools.Log
import java.util.Timer
import java.util.TimerTask

class CallViewModelFactory(private val call: Call) :
    ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return CallViewModel(call) as T
    }
}

open class CallViewModel(val call: Call) : GenericContactViewModel(call.remoteAddress) {
    val address: String by lazy {
        Module_Phone_LinphoneUtils.getDisplayableAddress(call.remoteAddress)
    }

    val isPaused = MutableLiveData<Boolean>()

    val isEarpieceSupport = MutableLiveData<Boolean>()

    val isOutgoingEarlyMedia = MutableLiveData<Boolean>()

    val callEndedEvent: MutableLiveData<Module_Phone_Event<Boolean>> by lazy {
        MutableLiveData<Module_Phone_Event<Boolean>>()
    }

    val callReleasedEvent: MutableLiveData<Module_Phone_Event<Boolean>> by lazy {
        MutableLiveData<Module_Phone_Event<Boolean>>()
    }

    val callConnectedEvent: MutableLiveData<Module_Phone_Event<Boolean>> by lazy {
        MutableLiveData<Module_Phone_Event<Boolean>>()
    }

    val curCallIsMonitor = MutableLiveData<Boolean>()
    val curCallIsVideoEnable = MutableLiveData<Boolean>()

    //远端是否为视频呼叫
    val inComingCallIsVideoEnable = MutableLiveData<Boolean>()

    /**
     * 根据视频方向来判断显示语音/视频接听
     * 由于目前暂时将视频预览关闭,无法判断来电的视频方向状态
     * 目前根据 $inComingCallIsVideoEnable 来显示语音/视频接听按钮
     */
    val curMediaDirectionIsSendOnly = MutableLiveData<Boolean>()
    var curNativePreviewWindowIdIsLocal = MutableLiveData<Boolean>()
    var curCallBaseTime: Long = 0L
    var curCallRemoteDisplayName = MutableLiveData<String>()
    var curCallRemoteAvatarString = MutableLiveData<String>()
    var curCallRemoteDeviceType = MutableLiveData<Module_Phone_DeviceType>()
    val showRouteDialog = MutableLiveData<Boolean>()
    val audioRouteChanged = MutableLiveData<Module_Phone_Event<Boolean>>()
    val curDeviceType = MutableLiveData<AudioDevice.Type>()


    val enteredDTMF = MutableLiveData<String>()
    private var enteredDTMFCursorPosition: Int = 0
    val mDialerEditText = MutableLiveData<EditText>()
    private val vibrator =
        coreContext.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    val mShowDialerPlate = MutableLiveData<Boolean>()

    private var timer: Timer? = null

    val onKeyClick: Module_Phone_NumpadDigitListener = object : Module_Phone_NumpadDigitListener {
        override fun handleClick(key: Char) {
            if (mDialerEditText.value?.selectionStart == null) {
                enteredDTMFCursorPosition = 0
            } else {
                enteredDTMFCursorPosition = mDialerEditText.value?.selectionStart!!
            }
            if (enteredDTMFCursorPosition < 0) enteredDTMFCursorPosition = 0

            val sb: StringBuilder = StringBuilder(enteredDTMF.value)
            try {
                sb.insert(enteredDTMFCursorPosition, key.toString())
            } catch (ioobe: IndexOutOfBoundsException) {
                sb.insert(sb.length, key.toString())
            }
            enteredDTMF.value = sb.toString()

            if (vibrator.hasVibrator() && NL_App.corePreferences.dtmfKeypadVibration) {
                Module_Phone_Compatibility.eventVibration(vibrator)
            }

            val sendDtmf = call.sendDtmf(key)
            Log.e("[Call View Model] Send DTMF $key ,result:$sendDtmf")
        }

        override fun handleLongClick(key: Char): Boolean {
            if (mDialerEditText.value?.selectionStart == null) {
                enteredDTMFCursorPosition = 0
            } else {
                enteredDTMFCursorPosition = mDialerEditText.value?.selectionStart!!
            }
            if (enteredDTMFCursorPosition < 0) enteredDTMFCursorPosition = 0
            val sb: StringBuilder = StringBuilder(enteredDTMF.value)
            try {
                sb.insert(enteredDTMFCursorPosition, key.toString())
            } catch (ioobe: IndexOutOfBoundsException) {
                sb.insert(sb.length, key.toString())
            }
            enteredDTMF.value = sb.toString()

            val sendDtmf = call.sendDtmf(key)
            Log.e("[Call View Model] Send DTMF $key ,result:$sendDtmf")
            return true
        }
    }

    private val coreListener = object : CoreListenerStub() {
        override fun onCallStateChanged(
            core: Core,
            call: Call,
            state: Call.State?,
            message: String,
        ) {
            isPaused.value = state == Call.State.Paused
            isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia

            if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
                if (state == Call.State.End) {
                    callEndedEvent.value = Module_Phone_Event(true)
                } else if (state == Call.State.Released || state == Call.State.Error) {
                    callReleasedEvent.value = Module_Phone_Event(true)
                }
                if (state == Call.State.Error) {
                    Log.e("[Call View Model] Error state reason is ${call.reason}")
                }
            } else if (state == Call.State.Connected) {
                callConnectedEvent.value = Module_Phone_Event(true)
            } else if (state == Call.State.StreamsRunning) {
                // Stop call update timer once user has accepted or declined call update
//                timer?.cancel()
            } else if (state == Call.State.UpdatedByRemote) {
                // User has 30 secs to accept or decline call update
                // Dialog to accept or decline is handled by CallsViewModel & ControlsFragment
//                startTimer(call)
            }
        }
    }

    private val listener = object : CallListenerStub() {
        override fun onStateChanged(call: Call, state: Call.State, message: String) {
            if (call != this@CallViewModel.call) return

            isPaused.value = state == Call.State.Paused
            isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia

            if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
                timer?.cancel()
                if (state == Call.State.End) {
                    callEndedEvent.value = Module_Phone_Event(true)
                } else if (state == Call.State.Released || state == Call.State.Error) {
                    callReleasedEvent.value = Module_Phone_Event(true)
                }
                if (state == Call.State.Error) {
                    Log.e("[Call View Model] Error state reason is ${call.reason}")
                }
            } else if (state == Call.State.Connected) {
                callConnectedEvent.value = Module_Phone_Event(true)
            } else if (state == Call.State.StreamsRunning) {
                // Stop call update timer once user has accepted or declined call update
                timer?.cancel()
            } else if (state == Call.State.UpdatedByRemote) {
                // User has 30 secs to accept or decline call update
                // Dialog to accept or decline is handled by CallsViewModel & ControlsFragment
                startTimer(call)
            }
        }

        override fun onSnapshotTaken(call: Call, filePath: String) {
            Log.i("[Call View Model] Snapshot taken, saved at $filePath")
            val content = Factory.instance().createContent()
            content.filePath = filePath
            content.type = "image"
            content.subtype = "jpeg"
            content.name = filePath.substring(filePath.indexOf("/") + 1)

//            viewModelScope.launch {
//                if (Compatibility.addImageToMediaStore(coreContext.context, content)) {
//                    Log.i("[Call View Model] Adding snapshot ${content.name} to Media Store terminated")
//                } else {
//                    Log.e("[Call View Model] Something went wrong while copying file to Media Store...")
//                }
//            }
        }

        override fun onDtmfReceived(call: Call, dtmf: Int) {
            super.onDtmfReceived(call, dtmf)
            Log.e("[Call View Model]", "onDtmfReceived：DTMF:$dtmf")
        }
    }

    init {
//        call.addListener(listener)
        coreContext.core.addListener(coreListener)
//        curDeviceType.value = AudioDevice.Type.Earpiece
        isEarpieceSupport.value = checkAudioDeviceContainsEarpiece()
        Log.e("[Call View Model]", "isEarpieceSupport:${isEarpieceSupport.value}")
        isPaused.value = call.state == Call.State.Paused
        isOutgoingEarlyMedia.value = call.state == Call.State.OutgoingEarlyMedia

        enteredDTMF.value = ""
        mShowDialerPlate.value = false
        curNativePreviewWindowIdIsLocal.value = true
        if (curCallBaseTime == 0L) {
            curCallBaseTime = SystemClock.elapsedRealtime()
        }

        curCallIsVideoEnable.value =
            call.currentParams?.isVideoEnabled

        inComingCallIsVideoEnable.value =
            call.remoteParams?.isVideoEnabled

        val sipAccount = Module_Phone_VoipController.get().getRemoteSipAccount(call)
        val displayName =
            BaseApplication.getDeviceDataByUsername(sipAccount)?.devName ?: Module_Phone_VoipController.get()
                .getRemoteUsername()
        curCallRemoteDisplayName.value = displayName
        if (curCallRemoteDisplayName.value.isNullOrEmpty()) {
            curCallRemoteDisplayName.value = call.remoteAddress.domain
        }
        val callParams = call.remoteParams
        val remoteIP = callParams?.getCustomHeader(coreContext.KEY_HEADER_REMOTE_IP)
        if (!remoteIP.isNullOrEmpty()) {
            curCallRemoteDisplayName.value = remoteIP.toString()

            try {
                call.remoteAddress.setHeader(coreContext.KEY_HEADER_REMOTE_IP, remoteIP)
            } catch (e: Exception) {
            }
        }

        curCallRemoteAvatarString.value = ""
        curCallRemoteDeviceType.value = Module_Phone_DeviceType.PHONE

        val currentCall: Call? = coreContext.core.currentCall
        if (currentCall !== null) {
            var type = currentCall.params.getCustomHeader(NL_Key.CALL_INFO)
            curCallIsMonitor.value = type == NL_Key.EXTRA_MONITOR
            Log.e("[Call View Model]", "type：$type")
        }

        val videoDirection = call.currentParams.videoDirection
        curMediaDirectionIsSendOnly.value = videoDirection == MediaDirection.SendOnly
        Log.e(
            "[Call View Model]",
            "videoDirection：$videoDirection   curCallIsVideoEnable:${curCallIsVideoEnable.value} inComingCallIsVideoEnable:${inComingCallIsVideoEnable.value}"
        )
    }

    fun eraseLastChar() {
        enteredDTMF.value = enteredDTMF.value?.dropLast(1)
    }

    fun eraseChar() {
        if (mDialerEditText.value?.selectionStart == null) {
            enteredDTMFCursorPosition = 0
        } else {
            enteredDTMFCursorPosition = mDialerEditText.value?.selectionStart!!
        }
        if (enteredDTMFCursorPosition < 0) enteredDTMFCursorPosition = 0

        val sb: StringBuilder = StringBuilder(enteredDTMF.value)
        try {
            sb.deleteAt(enteredDTMFCursorPosition - 1)
        } catch (ioobe: IndexOutOfBoundsException) {
        }
        enteredDTMF.value = sb.toString()
    }

    fun eraseAll(): Boolean {
        enteredDTMF.value = ""
        return true
    }

    fun onBeforeUriChanged(editText: EditText, count: Int, after: Int) {
        enteredDTMFCursorPosition = editText.selectionEnd
        enteredDTMFCursorPosition += after - count
    }

    fun onAfterUriChanged(editText: EditText, editable: Editable?) {
        val newLength = editable?.length ?: 0
        if (newLength <= enteredDTMFCursorPosition) enteredDTMFCursorPosition = newLength
        if (enteredDTMFCursorPosition < 0) enteredDTMFCursorPosition = 0
        editText.setSelection(enteredDTMFCursorPosition)
    }

    fun onFocusUriChanged(editText: EditText) {
        mShowDialerPlate.value = true
        val inputMethodManager =
            NL_App.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
        inputMethodManager?.hideSoftInputFromWindow(editText.windowToken, 0)

        val timer: Timer = Timer()
        timer.schedule(
            object : TimerTask() {
                override fun run() {
                    val inputMethodManager =
                        NL_App.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?
                    inputMethodManager?.hideSoftInputFromWindow(editText.windowToken, 0)
                }
            },
            50
        )
    }

    override fun onCleared() {
        destroy()
        super.onCleared()
    }

    fun destroy() {
        coreContext.core.removeListener(coreListener)
    }

    fun answerCall(isVideo: Boolean) {
        coreContext.answerCall(call, isVideo)
    }

    fun terminateCall() {
        coreContext.terminateCall(call)
    }

    fun pause() {
        call.pause()
    }

    fun resume() {
        call.resume()
    }

    fun takeScreenshot() {
        if (call.currentParams.isVideoEnabled) {
            val fileName = System.currentTimeMillis().toString() + ".jpeg"
            call.takeVideoSnapshot(Module_Phone_FileUtils.getFileStoragePath(fileName).absolutePath)
        }
    }

    private fun startTimer(call: Call) {
        timer?.cancel()

        timer = Timer("Call update timeout")
        timer?.schedule(
            object : TimerTask() {
                override fun run() {
                    // Decline call update
                    viewModelScope.launch {
                        withContext(Dispatchers.Main) {
                            coreContext.answerCallVideoUpdateRequest(call, false)
                        }
                    }
                }
            },
            30000
        )
    }

    /**
     *音频设备是否包含听筒
     */
    private fun checkAudioDeviceContainsEarpiece(): Boolean {
        var has = false
        for (audioDevice in coreContext.core.audioDevices) {
            if (audioDevice.type == AudioDevice.Type.Earpiece) {
                has = true
                break
            }
        }
        return has
    }

    fun switchAudioRoute(type: AudioDevice.Type) {
        when (type) {
            AudioDevice.Type.Speaker -> RinoAudioUtils.changeToSpeaker(context.applicationContext)
            AudioDevice.Type.Bluetooth -> RinoAudioUtils.changeToBluetooth(context.applicationContext)
            AudioDevice.Type.Earpiece -> RinoAudioUtils.changeToEarpiece(context.applicationContext)
            AudioDevice.Type.Headset -> RinoAudioUtils.changeToHeadset(context.applicationContext)
            else -> {
                Module_Phone_AudioRouteUtils.routeAudioToSpeaker()
            }
        }
        curDeviceType.value = type
    }

    fun getSupportAudioRouteList(): List<OptionItem> {
        val list = mutableListOf<OptionItem>()
        if (isHeadsetAvailable()) {
            list.add(
                OptionItem(
                    R.drawable.icon_headset_normal,
                    R.drawable.icon_headset_selected,
                    context.getString(R.string.route_type_headset),
                    AudioDevice.Type.Headset
                )
            )
        } else {
            list.add(
                OptionItem(
                    R.drawable.icon_earpiece_normal,
                    R.drawable.icon_earpiece_selected,
                    context.getString(R.string.route_type_earpiece),
                    AudioDevice.Type.Earpiece
                )
            )
        }

        list.add(
            OptionItem(
                R.drawable.icon_speaker_normal,
                R.drawable.icon_speaker_selected,
                context.getString(R.string.route_type_speaker),
                AudioDevice.Type.Speaker
            )
        )

        if (isBluetoothAvailable()) {
            list.add(
                OptionItem(
                    R.drawable.icon_bt_normal,
                    R.drawable.icon_bt_selected,
                    context.getString(R.string.route_type_bt),
                    AudioDevice.Type.Bluetooth
                )
            )
        }

        return list
    }

    fun isBluetoothAvailable(): Boolean {
//        return AudioRouteUtils.isBluetoothAudioRouteAvailable()
        return RinoAudioUtils.isBluetoothA2dpOn(context.applicationContext) || RinoAudioUtils.isBluetoothScoOn(
            context.applicationContext
        )
    }

    fun isHeadsetAvailable(): Boolean {
//        return AudioRouteUtils.isHeadsetAudioRouteAvailable()
        return RinoAudioUtils.isWiredHeadsetOn(context.applicationContext)
    }

    fun getCurrentAudioRoute(): AudioDevice.Type {

        return if (RinoAudioUtils.isSpeakerphoneOn(context.applicationContext)) {
            AudioDevice.Type.Speaker
        } else if (RinoAudioUtils.isBluetoothScoOn(context.applicationContext)) {
            AudioDevice.Type.Bluetooth
        } else if (RinoAudioUtils.isWiredHeadsetOn(context.applicationContext)) {
            AudioDevice.Type.Headset
        } else {
            AudioDevice.Type.Earpiece
        }
    }

}
